{%- macro encode_value(source, encodable, depth) -%}
  {%- if encodable.is_nullable -%}
    {{encode_value(source, encodable.without_nullable(), depth + 1)}}?
  {%- elif encodable.is_optional -%}
    Optional<{{encode_value(source, encodable.without_optional(), depth + 1)}}>
  {%- elif encodable.is_list -%}
    List<{{encode_value(source, encodable.without_list(), depth + 1)}}>
  {%- elif encodable.is_struct -%}
    {%- set struct = encodable.get_underlying_struct() -%}
    matter.controller.cluster.structs.{{source.name}}Cluster{{struct.name}}
  {%- else -%}
    {{encodable.kotlin_type}}
  {%- endif -%}
{%- endmacro -%}

{%- macro encode_tlv(encodable, tag, name, depth) %}
  {%- if encodable.is_nullable -%}
      if ({{name}} != null) {
        {{encode_tlv(encodable.without_nullable(), tag, name, depth + 1)}}
      } else {
        putNull({{tag}})
      }
  {%- elif encodable.is_optional -%}
      if ({{name}}.isPresent) {
        val opt{{name}} = {{name}}.get()
        {{encode_tlv(encodable.without_optional(), tag, "opt" + name, depth + 1)}}
      }
  {%- elif encodable.is_list -%}
      startArray({{tag}})
      for (item in {{name}}.iterator()) {
        {{encode_tlv(encodable.without_list(), "AnonymousTag", "item", depth + 1)}}
      }
      endArray()
  {%- elif encodable.is_struct -%}
      {{name}}.toTlv({{tag}}, this)
  {%- else -%}
      put({{tag}}, {{name}})
  {%- endif -%}
{%- endmacro -%}

{%- macro decode_tlv(source, encodable, tag, depth) %}
  {%- if encodable.is_nullable -%}
      if (!tlvReader.isNull()) {
        {{decode_tlv(source, encodable.without_nullable(), tag, depth + 1)}}
      } else {
        tlvReader.getNull({{tag}})
        null
      }
  {%- elif encodable.is_optional -%}
      if (tlvReader.isNextTag({{tag}})) {
        Optional.of({{decode_tlv(source, encodable.without_optional(), tag, depth + 1)}})
      } else {
        Optional.empty()
      }
  {%- elif encodable.is_list -%}
      {%- set encodablewithoutlist = encodable.without_list() -%}
      buildList <{{encode_value(source, encodablewithoutlist, depth + 1)}}> {
        tlvReader.enterArray({{tag}})
        while(!tlvReader.isEndOfContainer()) {
          this.add({{decode_tlv(source, encodablewithoutlist, "AnonymousTag", depth + 1)}})
        }
        tlvReader.exitContainer()
      }
  {%- elif encodable.is_struct -%}
      {%- set struct = encodable.get_underlying_struct() -%}
      matter.controller.cluster.structs.{{source.name}}Cluster{{struct.name}}.fromTlv({{tag}}, tlvReader)
  {%- else -%}
      tlvReader.get{{encodable.kotlin_type}}({{tag}})
  {%- endif -%}
{%- endmacro -%}

{%- macro contextSpecificTag(field) -%}
  ContextSpecificTag(TAG_{{field.name | constcase}})
{%- endmacro -%}

/*
 *
 *    Copyright (c) 2023 Project CHIP Authors
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package matter.controller.cluster.eventstructs

import java.util.Optional
import matter.controller.cluster.*
import matter.tlv.AnonymousTag
import matter.tlv.ContextSpecificTag
import matter.tlv.Tag
import matter.tlv.TlvReader
import matter.tlv.TlvWriter

class {{cluster.name}}Cluster{{event.name}}Event(
{%- for field in event.fields -%}
  {%- set encodable = field | asEncodable(typeLookup) %}
  val {{field.name}}: {{encode_value(cluster, encodable, 0)}}
  {%- if loop.index0 < loop.length - 1 -%}{{","}}{%- endif -%}
{%- endfor %}
) {
  override fun toString(): String = buildString {
    append("{{cluster.name}}Cluster{{event.name}}Event {\n")
    {%- for field in event.fields %}
    append("\t{{field.name}} : ${{field.name}}\n")
    {%- endfor %}
    append("}\n")
  }

  fun toTlv(tlvTag: Tag, tlvWriter: TlvWriter) {
    tlvWriter.apply {
      startStructure(tlvTag)
      {% for field in event.fields %}
        {%- set encodable = field | asEncodable(typeLookup) %}
        {%- set tag = contextSpecificTag(field) -%}
        {{encode_tlv(encodable, tag, field.name, 0)}}
      {% endfor -%}
      endStructure()
    }
  }

  companion object {
    {%- for field in event.fields %}
    private const val TAG_{{field.name | constcase}} = {{field.code}}
    {%- endfor %}

    fun fromTlv(tlvTag: Tag, tlvReader: TlvReader) : {{cluster.name}}Cluster{{event.name}}Event {
      tlvReader.enterStructure(tlvTag)
      {% for field in event.fields %}
        {%- set decodable = field | asEncodable(typeLookup) %}
        {%- set tag = contextSpecificTag(field) -%}
        val {{field.name}} = {{decode_tlv(cluster, decodable, tag, 0)}}
      {% endfor %}
      tlvReader.exitContainer()

      return {{cluster.name}}Cluster{{event.name}}Event(
        {%- for field in event.fields -%}
          {%- set encodable = field | asEncodable(typeLookup) -%}
          {{field.name}}
          {%- if loop.index0 < loop.length - 1 -%}{{", "}}{%- endif -%}
        {%- endfor -%}
      )
    }
  }
}
